Super Adventure Island level notes by Revenant Started on 3/06/11, last updated 3/09/11, cleaned up on 1/01/12 These are some notes I collected on the level format and graphics compression for this game back when I was working on a level editor for it. I got bored of the game really quickly after I started doing this, so this'll probably be the last time I ever do anything with it. If you're interested in hacking this game you're welcome to upload updated versions of this document to RHDN or any other hacking sites. Offsets refer to the US version of the ROM. Have fun. ------------ Level format ------------ Offset 0x33F82 contains a table of pointers to level and cutscene headers. Each entry in the table is 4 bytes long: a 24-bit pointer followed by an additional byte which denotes the type of header (whether it is an actual level or one of the mode 7 cutscenes.) There are (I think) a total of 60 entries: entry 0 - opening scene (falling from sky) entry 1 - level 1-1 entry 2 - level 1-1's midway point entry 3 - level 1-2 ... The game stores 2 seperate headers for each level - one for the start, and one for the midway point. The two headers are mostly identical except for the player start point (though theoretically you could abuse this to make the player start in a totally different area if they die past the level's midway point). The byte following each pointer has these known bit values: 0x02 - get flown in by the bird 0x04 - skip level - weird. the game puts you at the level AFTER this one, with the correct sprites and numbering. 0x08 - normal level 0x80 - cutscene (using this for a normal level will just make the HUD and player vanish and you can't move or do anything) Headers are 0x6a (?) bytes long. The headers are pretty large and I hadn't bothered figuring out what each individual field does, but a bunch of them are probably pointers to graphics data etc. Level header format: Offs. | Size | Desc. --------+-------+--------------------- 0x00 | 1 | written to BG mode register and $0bfd | (should almost always be 0x79 except for cutscenes) 0x01 | 1 | written to $0c97 0x02 | 2 | written to $0c8d 0x04 | 2 | written to $0c8f 0x06 | 2 | written to $0c85 0x08 | 2 | written to $0c87 0x0a | 2 | written to $0c89 0x0c | 2 | written to $0c8b 0x0e | 2 | written to $009a 0x10 | 1 | written to $0c83 0x11 | 2 | pointer to HDMA table? (written to $0d80) 0x13 | 1 | written to $0d82 0x14 | 2 | written to $0dc0 (pointer to ???) 0x16 | 1 | written to $0dc2 0x17 | 2 | written to $0310 (pointer to ???) 0x19 | 1 | written to $0312 0x1a | 2 | written to $0313 0x1c | 1 | written to $0315 The format from here on varies depending on if RAM address $0bfd = $07. (cutscene vs. normal level). This is for standard levels. 0x1d | 2 | written to $0c29 0x1f | 2 | written to $0c2f 0x21 | 1 | width of level (screens) (written to $0c18) 0x22 | 1 | height of level (screens) (written to $0c19 and $0c2e) 0x23 | 1 | bank number for map pointers (written to $0c1c) 0x24 | 2 | pointer to screen indices (written to $0c1d) 0x26 | 2 | pointer to screen 0 data (written to $0c1f) 0x28 | 2 | pointer to metatiles (different part?)) (written to $0c21) 0x2a | 2 | pointer to metatiles? (written to $0c23) 0x2c | 2 | pointer to tile properties (written to $0c25) 0x2e | 2 | written to $0c4a 0x30 | 2 | written to $0c50 0x32 | 1 | written to $0c3b and $0c49 (value * 8 is written to $0c3d-0c3e) 0x33 | 1 | written to $0c3c and $0c4f 0x34 | 1 | written to $0c3f 0x35 | 2 | written to $0c40 0x37 | 2 | written to $0c42 0x39 | 2 | written to $0c44 0x3b | 2 | written to $0c46 0x3d | 2 | written to $0c6b 0x3f | 2 | written to $0c71 0x41 | 1 | written to $0c5c and $0c6a (value * 8 is written to $0c5e-0c5f) 0x42 | 1 | written to $0c5d and $0c70 0x43 | 1 | written to $0c60 0x44 | 2 | written to $0c61 0x46 | 2 | written to $0c63 0x48 | 2 | written to $0c65 0x4a | 2 | written to $0c67 0x4c | 3 | pointer to some compressed data written to $0050 0x4f | 3 | " " 0x52 | 3 | " " 0x55 | 3 | " " 0x58 | 3 | " " 0x5b | 3 | " " 0x5e | 3 | " " ... Screen data: Each screen is made up of 32x32 tiles. Number of horizontal + vertical screens defined in header. Screen 0 is pointed to in the header, subsequent screens are immediately after one another. Each screen is made up of 16bit tile numbers representing each 32x32 tile. Screen indices: The order in which these screens make up the level is stored in this list (pointed to in header again.) This is made up of m*n bytes where the first 'n' make up the top row of the level, and the last 'n' do the same for the last half (for a total of m rows.) Level 1-1 takes place entirely on the bottom of the stage, so there are a bunch of 0s at the beginning for that stage (where screen 0 is totally blank.) You can use numbers more than once to make level geometry repeat itself if you're lazy. ------------------- Compressed graphics ------------------- Offset 0x356c6 has pointers to compressed data for player animation frames. Offset 0x323f5 has pointers to compressed data for ??? (other stuff) The graphics use some kind of RLE-like compression scheme that uses bit shifting to determine how many times to write each byte of compressed data How a tile is decompressed: 1. Load the current byte of compressed data. 2. Let x = 0. 3. Do a bit shift left. 4. Each time the carry bit is set, set x to the next byte of the compressed data. 5. Write x to current memory offset, increment memory offset by two. Do steps 2-5 a total of eight times per tile. Examples: Compressed: 80 FF Decompressed: FF FF FF FF FF FF FF FF Compressed: 0F 01 02 03 04 Decompressed: 00 00 00 00 01 02 03 04 Compressed: CC FF 00 FF 00 Decompressed: FF 00 00 00 FF 00 00 00 Compressed: 00 Decompressed: 00 00 00 00 00 00 00 00 Compressed: FF 01 02 03 04 05 06 07 08 Decompressed: 01 02 03 04 05 06 07 08 Decompression order at beginning of game, before showing title screen: Even numbered bytes (00,02...) are loaded first, then odd numbered bytes are decompressed after. 07:8000 - 07:8054 -> 7F:2000 07:810D - 07:8165 -> 7F:2001 07:8055 - 07:80A6 -> 7F:2200 07:8166 - 07:81B1 -> 7F:2201 07:80A7 - 07:80DB -> 7F:2400 07:81B2 - 07:81DB -> 7F:2401 07:80DC - 07:810C -> 7F:2600 07:81DC - 07:8208 -> 7F:2601 etc... 7F:2000 - 7F:27FF are the first 4 horizontal rows of player sprite data in WRAM. Then the next 4 rows are loaded. Player sprites span most of the used space in bank 7 Graphics decompression routine begins at ROM address 03:AB23. variables: $42 - byte of graphics data $43 - byte being read in $44 - bytes remaning in this item $46 - number of items remaining to decompress for this row -- lda #$08 sta $44 stz $42 lda $0000, y ; ex: for loading sprites, y = $8000 and bank number = 7 sta $43 iny - asl $43 ; shift the byte left. bcs + ; if this results in an overflow, jump ahead lda $42 ; otherwise load the byte already at $42 and store it again. bra ++ + lda $0000, y ;on overflow: load the next byte, store it in $42 sta $42 iny ++ sta $7F2000,x inx inx dec $44 bne - ; if $44 is still positive, dec $46 bne --